package org.kiwi.data.config;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.kiwi.data.adapter.druid.DruidDataSourceAdapter;
import org.kiwi.data.annotation.TableShardingStrategyName;
import org.kiwi.data.interceptor.TableShardingInterceptor;
import org.kiwi.data.sharding.ClassPathShardingStrategyScanner;
import org.kiwi.data.util.AopUtil;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

import static org.kiwi.data.cons.DataConstant.*;
import static org.kiwi.data.util.ShardingUtil.scan;

/**
 * Created by jack08.liu on 2016/12/21.
 */
public class TableShardingBeanDefinitionParser implements BeanDefinitionParser {

    private static final String DRUID_DATA_SOURCE_ADAPTER = DruidDataSourceAdapter.class.getName();
    private static final String MODE_NORMAL = "normal";
    private static final String PROPERTY_MODE = "mode";

    private ClassPathShardingStrategyScanner classPathTableShardingStrategyScanner;

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {

        addTableShardingStrategyNameAnnotationInClassPathScanner(parserContext);

        updateConfigDataSourceBeanDefinition(parserContext, element.getAttribute(ATTRIBUTE_CONNECTION_MODE));

        registerTableShardingInterceptor(parserContext, element.getAttribute(ATTRIBUTE_STRATEGY_SCAN_PACKAGE));

        registerTableShardingAopConfig(element, parserContext, element.getAttribute(ATTRIBUTE_POINTCUT_EXPRESSION));

        return null;
    }

    private void addTableShardingStrategyNameAnnotationInClassPathScanner(ParserContext parserContext) {
        this.classPathTableShardingStrategyScanner = new ClassPathShardingStrategyScanner(parserContext.getRegistry());
        this.classPathTableShardingStrategyScanner.setShardingStrategyClass(TableShardingStrategyName.class);
        this.classPathTableShardingStrategyScanner.registerFilters();
    }

    private void updateConfigDataSourceBeanDefinition(ParserContext parserContext, String connectionMode) {
        if (!StringUtils.equalsIgnoreCase(MODE_NORMAL, connectionMode)) {
            String[] beanNames = parserContext.getRegistry().getBeanDefinitionNames();
            if (ArrayUtils.isNotEmpty(beanNames)) {
                for (String beanName : beanNames) {
                    BeanDefinition beanDefinition = parserContext.getRegistry().getBeanDefinition(beanName);
                    try {
                        if (StringUtils.equals(DRUID_DATA_SOURCE_ADAPTER, beanDefinition.getBeanClassName())) {
                            beanDefinition.getPropertyValues().addPropertyValue(PROPERTY_MODE, connectionMode);
                        }
                    } catch (Exception e) {
                        //ignore
                        continue;
                    }
                }
            }
        }
    }

    private void registerTableShardingInterceptor(ParserContext parserContext, String strategyPackage) {
        RootBeanDefinition tableShardingInterceptor = new RootBeanDefinition(TableShardingInterceptor.class);
        ManagedMap<String, BeanDefinitionHolder> shardingStrategies = scan(this.classPathTableShardingStrategyScanner, strategyPackage);
        tableShardingInterceptor.getPropertyValues().add(TABLE_SHARDING_STRATEGIES, shardingStrategies);
        parserContext.getRegistry().registerBeanDefinition(TABLE_SHARDING_INTERCEPTOR, tableShardingInterceptor);
    }

    private void registerTableShardingAopConfig(Element element, ParserContext parserContext, String pointCutExpression) {
        AopUtil.registerAopConfig(
                element,
                parserContext,
                TABLE_SHARDING_POINTCUT,
                pointCutExpression,
                TABLE_SHARDING_INTERCEPTOR,
                TABLE_SHARDING_ADVICE_ORDER
        );
    }

}
